/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.viewer;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage;
import javax.swing.JPanel;

/**
 * Simple viwer for rendered images, allows for zoom in/out, tile grid display
 *
 * @author Andrea Aime
 */
class ZoomableImageDisplay extends JPanel {

    /** The image to display. */
    protected RenderedImage image;

    protected RenderedImage rescaledImage;

    protected double scale = 1.0;

    protected boolean tileGridVisible = true;

    protected boolean useRescaled = false;

    /** Constructs a <code>ZoomableImageDisplay</code> and sets its the layout to <code>null</code>. */
    public ZoomableImageDisplay() {
        setLayout(null);
    }

    /**
     * Constructs a <code>ZoomableImageDisplay</code>, sets its layout to <code>null</code>, and sets its displayed
     * image.
     *
     * <p>The preferred size is set such that its width is the image width plus the left and right insets and its height
     * is the image height plus the top and bottom insets.
     *
     * @param image The image to display.
     * @throws IllegalArgumentException if <code>image</code> is <code>null</code>.
     */
    public ZoomableImageDisplay(RenderedImage image) {
        this();
        setImage(image);
    }

    public void setScale(double scale) {
        if (scale <= 0) {
            throw new IllegalArgumentException("Scale must be a positive number");
        }
        this.scale = scale;
        refreshComponent(useRescaled && rescaledImage != null ? rescaledImage : image);
    }

    public double getScale() {
        return scale;
    }

    /**
     * Sets a new image to display.
     *
     * @param image The image to display.
     * @throws IllegalArgumentException if <code>im</code> is <code>null</code>.
     */
    public void setImage(RenderedImage image) {
        if (image == null) {
            throw new IllegalArgumentException("Image displayed cannot be null");
        }

        this.useRescaled = false;
        this.image = image;
        refreshComponent(image);
    }

    public void setRescaledImage(RenderedImage image) {
        this.useRescaled = image != null;
        this.rescaledImage = image;
        if (image != null) {
            refreshComponent(image);
        }
    }

    public RenderedImage getImage() {
        return image;
    }

    private void refreshComponent(RenderedImage image) {
        if (image != null) {
            int w = (int) Math.ceil(image.getWidth() * scale);
            int h = (int) Math.ceil(image.getHeight() * scale);
            int minX = (int) Math.ceil(image.getMinX() * scale);
            int minY = (int) Math.ceil(image.getMinY() * scale);
            Insets insets = getInsets();
            Dimension dim = new Dimension(w + insets.left + insets.right + minX, h + insets.top + insets.bottom + minY);

            setPreferredSize(dim);
        }
        revalidate();
        repaint();
    }

    @Override
    public synchronized void paintComponent(Graphics g) {
        RenderedImage image = this.image;
        if (useRescaled && rescaledImage != null) {
            image = this.rescaledImage;
        }
        if (g instanceof Graphics2D) {
            Graphics2D g2d = (Graphics2D) g;
            // clear damaged area
            Rectangle clipBounds = g2d.getClipBounds();
            g2d.setColor(getBackground());
            g2d.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);

            if (image == null) {
                return;
            }

            // Translation moves the entire image within the container
            try {
                g2d.drawRenderedImage(image, AffineTransform.getScaleInstance(scale, scale));
            } catch (Throwable e) {
                e.printStackTrace();
            }

            if (tileGridVisible) {
                g2d.setColor(Color.WHITE);
                g2d.setXORMode(Color.BLACK);

                // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP));
                final int x = (int) Math.floor(image.getTileGridXOffset() * scale);
                final int y = (int) Math.floor(image.getTileGridYOffset() * scale);
                final int th = (int) Math.round(image.getTileHeight() * scale);
                final int tw = (int) Math.round(image.getTileWidth() * scale);
                final int xCount = image.getNumXTiles();
                final int yCount = image.getNumYTiles();
                final int minty = image.getMinTileY();
                final int mintx = image.getMinTileX();
                for (int i = mintx; i < (xCount + mintx + 1); i++) {
                    g2d.drawLine(x + (tw * i), y + (th * minty), x + (tw * i), y + (th * (yCount + minty)));
                }
                for (int j = minty; j < (yCount + minty + 1); j++) {
                    g2d.drawLine(x + (mintx * tw), y + (th * j), x + (tw * (xCount + mintx)), y + (th * j));
                }
            }
        }
    }

    public boolean isTileGridVisible() {
        return tileGridVisible;
    }

    public void setTileGridVisible(boolean visible) {
        this.tileGridVisible = visible;
        repaint();
    }

    public boolean isUseRescaled() {
        return useRescaled;
    }

    public void setUseRescaled(boolean useRescaled) {
        this.useRescaled = useRescaled;
    }
}
